package org.kth.dks.dks_comm;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;

import org.kth.dks.dks_node.DKSNode;
import org.kth.dks.util.CommunicationInfo;
import org.kth.dks.util.NodeInfo;


public class WebServer implements Runnable {
	
	private final ServerSocket serverSocket;
	
	private boolean finish = false;
	
	private ConnectionManager cm;
	
	private String hostname;
	
	private String ip;
	
	private String port;
    
    private ThreadPool threadPool;
	
	public class WebInstance implements Runnable {
		private Socket mySock;
		
		private PrintWriter out = null;
		
		public WebInstance(Socket mySock) {
			this.mySock = mySock;
		}
		
		public void closeSock() {
			try {
				mySock.close();
			} catch (IOException io) {
				io.printStackTrace();
			}
		}
		
		public String htmlUrl(DKSRef r) {
			return "<a href=\"" + r.getDKSWebURL() + "/info.html\">[Node "
			+ r.getID() + "]</a>";
		}
		
		public void printError(String errMsg) throws IOException {
			out.write("HTTP/1.0 200 OK\n");
			out.write("Content-type: text/html\n\n");
			out.flush();
			out.write("<html><head><link href=\"http://dks.sics.se/"
					+ "bt2_main.css\" rel=\"stylesheet\" type="
					+ "\"text/css\"></head><body>");
			out.write("<h2><b>Error: </b>"+errMsg+"</h2>\n");
			out.write("</body></html>");
			out.flush();
		}
		
		public void printInfo(DKSNode node) throws IOException {
			out.write("HTTP/1.0 200 OK\n");
			out.write("Content-type: text/html\n\n");
			out.flush();
			out
			.write("<html><head><link href=\"http://dks.sics.se/bt2_main.css\" "
					+ "rel=\"stylesheet\" type=\"text/css\"></head><body>");
			
			out.write("<h1>DKSNode: " + htmlUrl(node.getDKSRef()) + "</h1>");
			
			out
			.write("<table cellspacing=10 cellpadding=10><tr><th>Routing Info</th><th>Statistics</th>"
					+ "<th>System Info</th></tr>");
			
			out.write("<tr><td>");
			NodeInfo ni = node.getNodeInfo();
			CommunicationInfo ci = node.getComInfo();
			out.write("<b>Node Status</b>: " + ni.status.toString()+"<br>");
			out.write("<b>Successor</b>:   " + htmlUrl(ni.successor) + "<br>");
			out.write("<b>Predecessor</b>: " + htmlUrl(ni.predecessor));
			out.write("<br><b>Backlist</b>: ");
			for (int x = 0; x < ni.backList.length - 1; x++)
				out.write(htmlUrl(ni.backList[x]) + ",");
			if (ni.backList.length > 0)
				out.write(htmlUrl(ni.backList[ni.backList.length - 1]));
			
			out.write("<br><b>Frontlist</b>: ");
			for (int x = 0; x < ni.frontList.length - 1; x++)
				out.write(htmlUrl(ni.frontList[x]) + ",");
			if (ni.frontList.length > 0)
				out.write(htmlUrl(ni.frontList[ni.frontList.length - 1]));
			
			out.write("<br><b>Routing Table (unique entries)</b>:<br>");
			
			DKSRef tmp = null;
			for (int l = ni.levels; l >= 1; l--) {
				for (int i = 1; i < ni.kFactor; i++) {
					if (tmp != null
							&& tmp 
							.equals(ni.routingTable[l - 1][i].responsible))
						break;
					tmp = ni.routingTable[l - 1][i].responsible;
					out.write("lev:" + l + " int:" + i + " ["
							+ ni.routingTable[l - 1][i].begin + ", "
							+ ni.routingTable[l - 1][i].end + "): "
							+ htmlUrl(ni.routingTable[l - 1][i].responsible)
							+ "<br>");
				}
			}
			
			out.write("</td><td>");
			
			out.write("<b>Byte out</b>: " + ci.bytesSent + "<br>");
			out.write("<b>Byte in</b>: " + ci.bytesReceived + "<br>");
			out.write("<b>Delivered</b>: " + ci.msgsDelivered + "<br>");
            out.write("<b>Failed</b>: " + ci.msgsFailed + "<br>");
            out.write("<b>Received</b>: " + ci.msgsReceived + "<br>");
            out.write("<b>Unacked</b>: " + ci.msgsUnacked + "<br>");
            out.write("<b>Open conns</b>: " + ci.establishedConnections + "<br>");
            out.write("<b>Total conns</b>: " + ci.totalConnections + "<br>");
			
			out.write("</td><td>");
			out.write("<b>OS</b>: " + System.getProperty("os.name") + " "
					+ System.getProperty("os.version") + " ("
					+ System.getProperty("os.arch") + ")<br>");
			out.write("<b>JVM</b>: " + System.getProperty("java.vm.name")
					+ "<br>");
			out.write("<b>JVM Version</b>: "
					+ System.getProperty("java.vm.version") + "<br>");
			out.write("<b>Hostname</b>: " + hostname + "<br>");
			out.write("<b>IP Address</b>: " + ip + "<br>");
			out.write("<b>Port</b>: " + port + "<br>");
			out.write("<b>N</b>: " + DKSNode.N + "<br>");
			out.write("<b>K</b>: " + DKSNode.K + "<br>");
			out.write("<b>L</b>: " + DKSNode.L + "<br>");
			out.write("<b>F</b>: " + DKSNode.F + "<br>");
			
			out.write("</td></tr>");
			out.write("</table></body></html>");
		}
		
		public void cmdCgi(String rest) throws IOException {
			Map cgiArgs = new HashMap();
			StringTokenizer st = new StringTokenizer(rest, "/& ");
			if (!st.hasMoreTokens()) { printError("CGI parse error: "+rest); throw new IllegalArgumentException(); }
			else 
				while (st.hasMoreElements()) {
					String tok = st.nextToken();
					StringTokenizer eq = new StringTokenizer(tok, "=&");
					if (!eq.hasMoreTokens()) { printError("CGI parse error: "+tok); throw new IllegalArgumentException(); }
					String key = eq.nextToken();
					if (!eq.hasMoreTokens()) 
						break;
					String value = eq.nextToken();
					cgiArgs.put(key.toUpperCase(), value);
				}
			
			
//			String s = "<br>";
//			for(Iterator it = cgiArgs.entrySet().iterator(); it.hasNext(); ) {
//				Entry entry = (Entry)it.next();
//				s+="key:"+entry.getKey()+" = value:"+entry.getValue()+"<br>\n";
//			}
//			printError(s);
			
		}
		
		public void cmdInfo(String rest) throws IOException {
			StringTokenizer st = new StringTokenizer(rest, " /\n\r");
			long ring = -1;
			long id = -1;
			long guid = -1;
			
			if (st.hasMoreTokens())
				ring = Long.parseLong(st.nextToken());
			if (st.hasMoreTokens())
				id = Long.parseLong(st.nextToken());
			if (st.hasMoreTokens())
				guid = Long.parseLong(st.nextToken());
			
			DKSNode node = (DKSNode) cm.nodes.get(new DKSOverlayAddress(id,
					ring, guid));
			
			if (node != null) {
				printInfo(node);
			} else {
				printError("DKSNode with identifier "+id+" does not exist");
			}
		}
		
		public void run() {
			try {
				out = new PrintWriter(mySock.getOutputStream());
				InputStreamReader ir = new InputStreamReader(mySock
						.getInputStream());
				BufferedReader in = new BufferedReader(ir);
				String req = in.readLine();
				String rest = req;
				
				while (req != null && !rest.equals("") && rest != null) {
					rest = in.readLine();
				}
				
				StringTokenizer st = new StringTokenizer(req, " /?\n\r");
				if (!st.hasMoreTokens()) { printError("HTTP parse error: "+req); throw new IllegalArgumentException(); }
				String httpCmd = st.nextToken(); // get ridd of the first "GET"
				
				if (httpCmd.equalsIgnoreCase("GET")) {
					if (!st.hasMoreTokens()) { printError("HTTP parse error: "+req); throw new IllegalArgumentException(); }
					String dksCmd = st.nextToken();
					if (!st.hasMoreTokens()) { printError("HTTP parse error: "+req); throw new IllegalArgumentException(); }
					String dksArgs = st.nextToken("\n");
					
					if (dksCmd.equalsIgnoreCase("info"))
						cmdInfo(dksArgs);
					else if (dksCmd.equalsIgnoreCase("cgi"))
						cmdCgi(dksArgs);
					else
						printError("Unsupported DKS command: "+dksCmd);
					
				} else {
					printError("Unsupported HTTP command "+httpCmd);
				}
				
				out.flush();
				mySock.close();
			} catch (IOException ex) {
				ex.printStackTrace();
			} catch (Exception ex) {
				ex.printStackTrace();
				closeSock();
			}
		}
	};
	
	public WebServer(ConnectionManager c, int port) throws IOException {
		serverSocket = new ServerSocket(port);
		cm = c;
        threadPool = ThreadPool.getInstance();
	}
	
	public void run() {
		while (!getFinish()) {
			try {
				Socket socket = null;
				socket = serverSocket.accept();
				if (socket != null) {
					threadPool.addJob(new WebInstance(socket));
				}
			} catch (SocketTimeoutException ste) {
				ste.printStackTrace();
			} catch (Exception ex1) {
				ex1.printStackTrace();
			}
		}
	}
	
	public synchronized boolean getFinish() {
		return finish;
	}
	
	public synchronized void stop() {
		finish = true;
	}
	
	public void setHostname(String hostname) {
		this.hostname = hostname;
	}
	
	public void setIp(String ip) {
		this.ip = ip;
	}
	
	public void setPort(String port) {
		this.port = port;
	}
}
